﻿
using Moq;
using System;
using System.IO;
using Xunit;
using Xunit.Extensions;

namespace EmperialApps.WeatherSpark.Data {

    public class TestForecastInfo {

        [Fact]
        public void GetIdentifierFromTileUri_GivenInvalidUri_ReturnsTileIdSegment( ) {
            string tileUri = "/";
            string expected = "";

            string actual = ForecastInfo.GetIdentifierFromTileUri( tileUri );

            Assert.Equal( expected, actual );
        }

        [Theory]
        [InlineData( "Tile\\Path?TileId=4&Extra" )]
        [InlineData( "Tile/Path?Extra&TileId=4" )]
        public void GetIdentifierFromTileUri_GivenValidUri_ReturnsTileIdSegment( string tileUri ) {
            string expected = "TileId=4";

            string actual = ForecastInfo.GetIdentifierFromTileUri( tileUri );

            Assert.Equal( expected, actual );
        }

        [Fact]
        public void CreateIdentifier_GivenZeroId_ReturnsNull( ) {
            int tileId = 0;
            string expected = null;

            string actual = ForecastInfo.CreateIdentifier( tileId );

            Assert.Equal( expected, actual );
        }

        [Fact]
        public void CreateIdentifier_GivenNonZeroId_ReturnsFullIdentifier( ) {
            int tileId = 4;
            string expected = "TileId=4";

            string actual = ForecastInfo.CreateIdentifier( tileId );

            Assert.Equal( expected, actual );
        }

        [Fact]
        public void TryGetTileId_GivenNull_ReturnsZero( ) {
            string identifier = null;
            var expected = Tuple.Create( false, 0 );

            int tileId;
            var actual = Tuple.Create( ForecastInfo.TryGetTileId( identifier, out tileId ), tileId );

            Assert.Equal( expected, actual );
        }

        [Fact]
        public void TryGetTileId_GivenInvalidIdentifierString_ReturnsZero( ) {
            string identifier = "Invalid";
            var expected = Tuple.Create( false, 0 );

            int tileId;
            var actual = Tuple.Create( ForecastInfo.TryGetTileId( identifier, out tileId ), tileId );

            Assert.Equal( expected, actual );
        }

        [Fact]
        public void TryGetTileId_GivenInvalidIdentifierNumber_ReturnsZero( ) {
            string identifier = "TileId=Invalid";
            var expected = Tuple.Create( false, 0 );

            int tileId;
            var actual = Tuple.Create( ForecastInfo.TryGetTileId( identifier, out tileId ), tileId );

            Assert.Equal( expected, actual );
        }

        [Fact]
        public void TryGetTileId_GivenValidIdentifier_ReturnsTileId( ) {
            string identifier = "TileId=4";
            var expected = Tuple.Create( true, 4 );

            int tileId;
            var actual = Tuple.Create( ForecastInfo.TryGetTileId( identifier, out tileId ), tileId );

            Assert.Equal( expected, actual );
        }


        [Fact]
        public void Encode_ReturnsSafeString( ) {
            var info = new ForecastInfo( new Coordinate( 12.34, 56.78 ), "ID", ForecastTileMode.ImperialDay, "AnyTown, USA", Place.YrnoAddress, TimeSpan.FromMinutes( 60 ), DateTimeOffset.Now, ForecastSourceId.YRNO, 2 );

            string encoded = info.Encode( );
            string actual = Uri.EscapeDataString( encoded );
            string testUri = "/path?" + actual;

            Assert.True( Uri.IsWellFormedUriString( testUri, UriKind.RelativeOrAbsolute ) );
        }

        [Theory]
        [InlineData( 0 )]
        [InlineData( 1 )]
        [InlineData( 2 )]
        [InlineData( 3 )]
        public void Decode_GivenValueFromEncode_ReturnsOriginalValues( int state ) {
            ForecastInfo expected;
            switch( state ) {
                default:
                case 0:
                    expected = default( ForecastInfo );
                    break;
                case 1:
                    expected = new ForecastInfo( new Coordinate( 12.34, 56.78 ), "ID1", ForecastTileMode.ImperialHour, "Title1", "URI1", TimeSpan.FromMinutes( 30 ), default( DateTimeOffset ), default( ForecastSourceId ), default( uint ) );
                    break;
                case 2:
                    expected = new ForecastInfo( new Coordinate( -98.7, 65.43 ), "ID2", ForecastTileMode.MetricDay, "Title2", "URI2", default( TimeSpan ), DateTimeOffset.UtcNow, ForecastSourceId.NOAA, 1 );
                    break;
                case 3:
                    expected = new ForecastInfo( new Coordinate( 12.34, 56.78 ), "ID", ForecastTileMode.ImperialDay, "AnyTown, USA", Place.YrnoAddress, TimeSpan.FromMinutes( 60 ), DateTimeOffset.Now, ForecastSourceId.YRNO, 2 );
                    break;
            }

            string encoded = expected.Encode( );
            var actual = ForecastInfo.Decode( encoded );

            Assert.Equal( expected, actual );
        }


        [Fact]
        public void Save_DoesNotCloseStream( ) {
            var streamMock = new Mock<Stream>( );
            streamMock.SetupGet( m => m.CanWrite ).Returns( true );
            var forecastInfo = new ForecastInfo( );

            streamMock.Setup( m => m.Close( ) ).Throws<Xunit.Sdk.AssertException>( );

            Assert.DoesNotThrow( ( ) => ForecastInfo.Save( streamMock.Object, forecastInfo ) );
        }

        [Fact]
        public void Load_GivenInvalidStream_ThrowsFormatException( ) {
            using( var stream = new MemoryStream( ) )
            using( var writer = new BinaryWriter( stream ) ) {
                writer.Write( (short)1 );
                writer.Write( ushort.MaxValue );
                stream.Position = 0;

                var ex = Assert.Throws<FormatException>( ( ) => ForecastInfo.Load( stream ) );

                Assert.Contains( "Unrecognized", ex.Message );
            }
        }

        [Theory]
        [InlineData( 0 )]
        [InlineData( 1 )]
        [InlineData( 2 )]
        [InlineData( 3 )]
        public void Load_GivenValueFromSave_ReturnsOriginalValues( int state ) {
            ForecastInfo[] expected;
            switch( state ) {
                default:
                case 0:
                    expected = new ForecastInfo[0];
                    break;
                case 1:
                    expected = new ForecastInfo[1];
                    break;
                case 2:
                    expected = new[] {
                        new ForecastInfo( new Coordinate( 12.34, 56.78 ), "ID1", ForecastTileMode.ImperialHour, "Title1", "URI1", TimeSpan.FromMinutes(30), default(DateTimeOffset), default(ForecastSourceId), default(uint) ),
                        new ForecastInfo( new Coordinate( -98.7, 65.43 ), "ID2", ForecastTileMode.MetricDay, "Title2", "URI2", default(TimeSpan), DateTimeOffset.UtcNow, ForecastSourceId.NOAA, 1 )
                    };
                    break;
                case 3:
                    expected = new[] { new ForecastInfo( new Coordinate( 12.34, 56.78 ), "ID", ForecastTileMode.ImperialDay, "AnyTown, USA", Place.YrnoAddress, TimeSpan.FromMinutes( 60 ), DateTimeOffset.Now, ForecastSourceId.YRNO, 2 ) };
                    break;
            }

            using( var stream = new MemoryStream( ) ) {
                ForecastInfo.Save( stream, expected );
                stream.Position = 0;

                var actual = ForecastInfo.Load( stream );

                Assert.Equal( expected, actual );
            }
        }

    }

}
